package com.byx.imclientutil.netty;

import android.util.Log;

import com.byx.imclientutil.config.ImConfig;
import com.byx.imclientutil.config.ImConnectionStatus;
import com.byx.imclientutil.config.ImProperties;
import com.byx.imclientutil.entity.BaseMessage;
import com.byx.imclientutil.entity.MessageStatus;
import com.byx.imclientutil.listener.OnApplicationLayerCallBack;
import com.byx.imclientutil.listener.OnConnectStatusListener;
import com.byx.imclientutil.listener.OnMessageStatusListener;
import com.byx.imclientutil.net.NetworkManager;
import com.byx.imclientutil.protobuf.ChatMessageProtobuf;
import com.byx.imclientutil.service.ImServiceClient;
import com.byx.imclientutil.utils.NioEventLoopGroupUtil;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;

/**
 * NettyWebSocketImServiceImpl
 *
 * @author Superb
 * @date 2021/2/9 17:08
 */
public class NettyWebSocketClientImpl implements ImServiceClient, NetworkManager.OnNetworkStateChangedObserver {
    private static final String TAG= NettyWebSocketClientImpl.class.getSimpleName();

    private static volatile NettyWebSocketClientImpl instance;
    /**
     * 属性信息
     */
    private ImProperties imProperties;
    /**
     * 监听连接状态
     */
    private OnConnectStatusListener mOnConnectStatusListener;
    /**
     * 应用层回调接口
     */
    private OnApplicationLayerCallBack mOnApplicationLayerCallBack;
    /**
     * 消息状态监听器
     */
    private OnMessageStatusListener mOnMessageStatusListener;
    /**
     * 消息转发器
     */
    private MessageDispatcher messageDispatcher;
    /**
     * 是否已经关闭
     */
    private volatile boolean isClosed = true;
    /**
     * 是否正在进行重连
      */
    private volatile boolean isReconnecting = false;
    /**
     * 是否已初始化成功
      */
    private boolean initialized = false;

    private int foregroundHeartbeatInterval = ImConfig.FOREGROUND_HEARTBEAT_INTERVAL;
    private int backgroundHeartbeatInterval = ImConfig.BACKGROUND_HEARTBEAT_INTERVAL;

    /**
     * app前后台状态
      */
    private int appStatus = ImConfig.APP_STATUS_FOREGROUND;


    private Bootstrap bootstrap;
    private Channel channel;

    /**
     * ims连接状态
      */
    private volatile ImConnectionStatus imConnectionStatus;
    /**
     * 线程池组
      */
    private NioEventLoopGroupUtil executors;
    /**
     * 网络是否可用标识
      */
    private boolean isNetworkAvailable;
    /**
     * 是否执行过连接，如果未执行过，在onAvailable()的时候，无需进行重连
      */
    private boolean isExecConnect = false;
    /**
     * 当前连接host
      */
    private String currentHost = null;
    /**
     * 当前连接port
      */
    private int currentPort = -1;
    private int heartbeatInterval;

    public static NettyWebSocketClientImpl getInstance() {
        if (instance == null) {
            synchronized (NettyWebSocketClientImpl.class){
                if (instance == null) {
                    instance = new NettyWebSocketClientImpl();
                }
            }
        }
        return instance;
    }

    /**
     * 初始化
     *
     * @param imProperties              配置的各种属性
     * @param onConnectStatusListener   网络连接状态监听器
     * @return boolean
     */
    @Override
    public boolean initialize(ImProperties imProperties, OnConnectStatusListener onConnectStatusListener) {
        if (imProperties == null) {
            Log.d(TAG, "初始化失败：imProperties is null.");
            initialized = false;
            return false;
        }
        this.imProperties = imProperties;
        this.mOnConnectStatusListener = onConnectStatusListener;
        messageDispatcher = new MessageDispatcher();

        executors = new NioEventLoopGroupUtil();
        // 初始化重连线程池
        executors.initBossLoopGroup();
        // 标识ims初始化成功
        initialized = true;
        // 标识ims已打开
        isClosed = false;
        return true;
    }

    /**
     * 连接
     */
    @Override
    public void connect(OnApplicationLayerCallBack onApplicationLayerCallBack) {
        if(!initialized) {
            Log.w(TAG, "IMS初始化失败，请查看日志");
            return;
        }

        this.mOnApplicationLayerCallBack = onApplicationLayerCallBack;
        messageDispatcher.setOnEventListener(onApplicationLayerCallBack);
        // 标识已执行过连接
        isExecConnect = true;
        this.reconnect(true);
    }

    /**
     * 重连
     *
     * @param isFirstConnect 是否是第一次连接
     */
    @Override
    public void reconnect(boolean isFirstConnect) {
        if (!isFirstConnect) {
            // 非首次连接，代表之前已经进行过重连，延时一段时间再去重连
            try {
                Thread.sleep(imProperties.getReconnectInterval());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        if (!isClosed && !isReconnecting) {
            synchronized (this) {
                if (!isClosed && !isReconnecting) {
                    // 标识正在进行重连
                    isReconnecting = true;
                    //回调连接状态
                    mOnConnectStatusListener.onConnecting();
                    // 关闭channel
                    closeChannel();
                    // 开启重连任务
                    executors.execBossTask(new NettyWebSocketReconnectRunnable(this));
                }
            }
        }
    }

    /**
     * @param msg
     */
    @Override
    public void sendMessage(ChatMessageProtobuf.Msg msg) {
        if(!initialized) {
            Log.w(TAG, "IMS初始化失败，请查看日志");
            return;
        }
        if(msg == null){
            Log.w(TAG, "发送消息失败，消息为空");
            return;
        }
        if(channel == null){
            Log.w(TAG, "发送消息失败，channel为空");
            return;
        }
        String messageId = msg.getMessageId();
        if (messageId == null || messageId.isEmpty()){
            Log.w(TAG, "发送消息失败，消息id为空");
            return;
        }
        Log.d(TAG, "sendMessage: "+msg.toString());
        try{
            channel.writeAndFlush(msg);
        }catch (Exception e){
            e.printStackTrace();
            Log.w(TAG, "发送消息失败，发生异常："+e.getMessage());
        }
    }

    /**
     * 获取重连间隔时长
     *
     * @return
     */
    @Override
    public int getReconnectInterval() {
        return imProperties.getReconnectInterval();
    }

    /**
     * 获取连接超时时长
     *
     * @return
     */
    @Override
    public int getConnectTimeout() {
        return imProperties.getConnectTimeout();
    }

    /**
     * 获取心跳间隔时间
     *
     * @return
     */
    public int getHeartbeatInterval() {
        return this.heartbeatInterval;
    }

    /**
     * 获取应用在前台时心跳间隔时间
     *
     * @return
     */
    @Override
    public int getForegroundHeartbeatInterval() {
        return foregroundHeartbeatInterval;
    }

    /**
     * 获取应用在后台时心跳间隔时间
     *
     * @return
     */
    @Override
    public int getBackgroundHeartbeatInterval() {
        return backgroundHeartbeatInterval;
    }

    /**
     * 设置app前后台状态
     *
     * @param appStatus
     */
    @Override
    public void setAppStatus(int appStatus) {
        this.appStatus = appStatus;
        if (this.appStatus == ImConfig.APP_STATUS_FOREGROUND) {
            heartbeatInterval = getForegroundHeartbeatInterval();
        } else if (this.appStatus == ImConfig.APP_STATUS_BACKGROUND) {
            heartbeatInterval = getBackgroundHeartbeatInterval();
        }

        addHeartBeatHandler();
    }

    /**
     * 获取由应用层构造的握手消息
     *
     * @return
     */
    @Override
    public ChatMessageProtobuf.Msg getHandshakeMsg() {
        if (mOnApplicationLayerCallBack != null) {
            return mOnApplicationLayerCallBack.getHandshakeMsg();
        }
        return null;
    }

    /**
     * 获取由应用层构造的心跳消息
     *
     * @return
     */
    @Override
    public ChatMessageProtobuf.Msg getHeartbeatMsg() {
        if (mOnApplicationLayerCallBack != null) {
            return mOnApplicationLayerCallBack.getHeartbeatMsg();
        }

        return null;
    }

    /**
     * 获取应用层消息发送状态报告消息类型
     *
     * @return
     */
    @Override
    public int getServerSentReportMsgType() {
        if (mOnApplicationLayerCallBack != null) {
            return mOnApplicationLayerCallBack.getServerSentReportMsgType();
        }
        return 0;
    }

    /**
     * 获取应用层消息接收状态报告消息类型
     *
     * @return
     */
    @Override
    public int getClientReceivedReportMsgType() {
        if (mOnApplicationLayerCallBack != null) {
            return mOnApplicationLayerCallBack.getClientReceivedReportMsgType();
        }

        return 0;
    }

    /**
     * 获取应用层消息发送超时重发次数
     *
     * @return
     */
    @Override
    public int getResendCount() {
        return imProperties.getResendCount();
    }

    /**
     * 获取应用层消息发送超时重发间隔
     *
     * @return
     */
    @Override
    public int getResendInterval() {
        return imProperties.getResendInterval();
    }

    /**
     * 获取消息转发器
     *
     * @return
     */
    @Override
    public MessageDispatcher getMsgDispatcher() {
        return messageDispatcher;
    }

    /**
     * 绑定消息状态监听器
     *
     * @param onMessageStatusListener 消息状态监听
     */
    @Override
    public void bindOnMessageStatusListener(OnMessageStatusListener onMessageStatusListener) {
        this.mOnMessageStatusListener = onMessageStatusListener;
    }

    /**
     * 关闭服务，释放资源
     */
    @Override
    public void release() {
        if (isClosed){
            return;
        }
        // 关闭channel
        closeChannel();
        // 关闭bootstrap
        closeBootstrap();
        // 标识未进行初始化
        initialized = false;
        isClosed = true;
        // 释放线程池组
        if(executors != null) {
            executors.destroy();
            executors = null;
        }
        isReconnecting = false;
    }

    @Override
    public void onNetworkAvailable() {
        this.isNetworkAvailable = true;
        if(!isExecConnect) {
            return;
        }
        Log.d(TAG, "网络可用，启动imService");
        this.isClosed = false;
        // 网络连接时，自动重连
        this.reconnect(false);
    }

    @Override
    public void onNetworkUnavailable() {
        this.isNetworkAvailable = false;
        if(!isExecConnect) {
            return;
        }
        Log.d(TAG, "网络不可用，关闭ims");
        this.isClosed = true;
        this.isReconnecting = false;
        // 网络断开时，销毁重连线程组（停止重连任务）
        executors.destroyBossLoopGroup();
        // 回调ims连接状态
        callbackImServiceConnectStatus(ImConnectionStatus.ConnectFailed_NetworkUnavailable);
        // 关闭channel
        closeChannel();
        // 关闭bootstrap
        closeBootstrap();
    }

    /**
     * 初始化bootstrap
     */
    void initBootstrap() {
        closeBootstrap();// 初始化前先关闭
        NioEventLoopGroup loopGroup = new NioEventLoopGroup(4);
        bootstrap = new Bootstrap();
        bootstrap.group(loopGroup).channel(NioSocketChannel.class)
                // 设置该选项以后，如果在两小时内没有数据的通信时，TCP会自动发送一个活动探测数据报文
                .option(ChannelOption.SO_KEEPALIVE, true)
                // 设置禁用nagle算法，如果要求高实时性，有数据发送时就马上发送，就将该选项设置为true关闭Nagle算法；如果要减少发送次数减少网络交互，就设置为false等累积一定大小后再发送。默认为false
                .option(ChannelOption.TCP_NODELAY, true)
                // 设置TCP发送缓冲区大小（字节数）
                .option(ChannelOption.SO_SNDBUF, 32 * 1024)
                // 设置TCP接收缓冲区大小（字节数）
                .option(ChannelOption.SO_RCVBUF, 32 * 1024)
                // 设置连接超时时长，单位：毫秒
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, imProperties.getConnectTimeout())
                // 设置初始化ChannelHandler
                .handler(new NettyWebSocketChannelInitializer(this));
    }

    /**
     * 关闭channel
     */
    private void closeChannel() {
        try {
            if (channel != null) {
                // 关闭channel时，需要先移除对应handler
                removeHandler(HeartBeatHandler.class.getSimpleName());
                removeHandler(NettyWebSocketHandler.class.getSimpleName());
                removeHandler(IdleStateHandler.class.getSimpleName());
                try {
                    channel.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                try {
                    channel.eventLoop().shutdownGracefully();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }finally {
            channel = null;
        }
    }

    /**
     * 移除handler
     * @param name
     */
    private void removeHandler(String name) {
        try {
            ChannelPipeline pipeline = channel.pipeline();
            if(pipeline.get(name) != null) {
                pipeline.remove(name);
            }
        }catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "移除handler失败：" + name);
        }
    }

    /**
     * 关闭bootstrap
     */
    private void closeBootstrap() {
        try {
            if (bootstrap != null) {
                bootstrap.config().group().shutdownGracefully();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            bootstrap = null;
        }
    }

    /**
     * 回调ims连接状态
     *
     * @param imConnectionStatus 连接状态
     */
    void callbackImServiceConnectStatus(ImConnectionStatus imConnectionStatus) {
        Log.d(TAG, "回调ims连接状态：" + imConnectionStatus);
        if(this.imConnectionStatus == imConnectionStatus) {
            Log.w(TAG, "连接状态与上一次相同，无需执行任何操作");
            return;
        }

        this.imConnectionStatus = imConnectionStatus;
        switch (imConnectionStatus) {
            case Unconnected:
                Log.w(TAG, "IMS未连接");
                if (mOnConnectStatusListener != null) {
                    mOnConnectStatusListener.onUnconnected();
                }
                break;

            case Connecting:
                Log.d(TAG, "IMS连接中");
                if (mOnConnectStatusListener != null) {
                    mOnConnectStatusListener.onConnecting();
                }
                break;

            case Connected:
                Log.d(TAG, String.format("ims连接成功，host『%s』, port『%s』", currentHost, currentPort));
                if (mOnConnectStatusListener != null) {
                    mOnConnectStatusListener.onConnected();
                }
                // 连接成功，发送握手消息
                ChatMessageProtobuf.Msg handshakeMsg = getHandshakeMsg();
                if (handshakeMsg != null) {
                    System.out.println("发送握手消息，message=" + handshakeMsg);
                    sendMessage(handshakeMsg);
                } else {
                    System.err.println("请应用层构建握手消息！");
                }
                break;

            case ConnectFailed:
            case ConnectFailed_IMSClosed:
            case ConnectFailed_ServerListEmpty:
            case ConnectFailed_ServerEmpty:
            case ConnectFailed_ServerIllegitimate:
            case ConnectFailed_NetworkUnavailable:
            default:
                int errCode = imConnectionStatus.getErrCode();
                String errMsg = imConnectionStatus.getErrMsg();
                Log.w(TAG, "errCode = " + errCode + "\terrMsg = " + errMsg);
                if (mOnConnectStatusListener != null) {
                    mOnConnectStatusListener.onConnectedFailed(errCode, errMsg);
                }
                break;
        }
    }

    void callBackMessageStatus(String msgId,MessageStatus messageStatus){
        if (mOnMessageStatusListener != null){
            switch (messageStatus){
                case SUCCEED:
                    mOnMessageStatusListener.onMessageSentSucceed(msgId);
                    break;
                case SENDING:
                    mOnMessageStatusListener.onMessageSending(msgId);
                    break;
                case FAILED:
                    mOnMessageStatusListener.onMessageSentFailed(msgId,null);
                    break;
            }
        }
    }

    NioEventLoopGroupUtil getExecutors() {
        return executors;
    }

    /**
     * 网络是否可用
     *
     * @return
     */
    boolean isNetworkAvailable() {
        if (mOnApplicationLayerCallBack != null){
            return mOnApplicationLayerCallBack.isNetworkAvailable();
        }
        return false;
    }

    /**
     * ims是否关闭
     *
     * @return
     */
    @Override
    public boolean isClosed() {
        return isClosed;
    }

    ImProperties getImProperties() {
        return imProperties;
    }

    Bootstrap getBootstrap() {
        return bootstrap;
    }

    void setChannel(Channel channel) {
        this.channel = channel;
    }

    /**
     * 标识是否正在进行重连
     * @param isReconnecting 重连标识
     */
    void setReconnecting(boolean isReconnecting) {
        this.isReconnecting = isReconnecting;
    }

    public void addHeartBeatHandler() {
        if (channel == null || !channel.isActive() || channel.pipeline() == null){
            return;
        }
        try {
            if (channel.pipeline().get(IdleStateHandler.class.getSimpleName()) != null){
                channel.pipeline().remove(IdleStateHandler.class.getSimpleName());
            }
            channel.pipeline().addFirst(IdleStateHandler.class.getSimpleName(),new IdleStateHandler(3*heartbeatInterval,heartbeatInterval,0));
            if (channel.pipeline().get(HeartBeatHandler.class.getSimpleName()) != null){
                channel.pipeline().remove(HeartBeatHandler.class.getSimpleName());
            }
            if (channel.pipeline().get(NettyWebSocketHandler.class.getSimpleName()) != null){
                channel.pipeline().addBefore(NettyWebSocketHandler.class.getSimpleName(),HeartBeatHandler.class.getSimpleName(),new HeartBeatHandler(this));
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void setOnMessageStatusListener(OnMessageStatusListener onMessageStatusListener) {
        this.mOnMessageStatusListener = onMessageStatusListener;
    }
}
